# Copyright 2009 Lee Harr
#
# This file is part of Acromania.
#
# Acromania is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Acromania is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Acromania.  If not, see <http://www.gnu.org/licenses/>.


import random

from twisted.internet import reactor
later = reactor.callLater


import amplayer
import amgame
import amdb
from colors import blue, red, green, yellow, magenta, white, bold
import conf

class GameServer(object):
    def __init__(self):
        self.players = []
        self.game = None
        self.trickle_rate = 0.5
        self.trickle_lines = []

    def broadcast(self, msg='', indent=0, color=True, exclude=None):
        for player in self.players:
            if player is not exclude:
                player.message(msg, indent=indent, color=color)

    def trickle(self, msg=''):
        '''Used like broadcast, to send a message to all connected clients, but
        limit the transmission of lines to 1 line every trickle_rate seconds.

        '''

        self.trickle_lines.append(msg)

    def trickle_send(self):
        if self.trickle_lines:
            line = self.trickle_lines.pop(0)
            for player in self.players:
                player.message(line)

        later(self.trickle_rate, self.trickle_send)

    def join(self, client):
        names = [p.name for p in self.players]
        player = amplayer.Player(client, names)
        self.players.append(player)

        if self.game is None or self.game.finished:
            print 'starting new game during gameserver.join'
            self.new_game(player)
            later(10, self.between_games)

        if player not in self.game.players:
            print 'player joining game'
            self.game.join(player)

            if not self.game.started:
                later(0, self.waiting)
            else:
                later(0, self.game.show_status, player)

        return player

    def create(self, player, r):
        'Try to create a new account.'

        try:
            name, pwtext = r.split()
        except ValueError:
            player.message('Create account with "/create <name> <password>"')
        else:
            if not amdb.exists(name):
                if player.username:
                    player.message('Please do not create multiple accounts.')
                else:
                    player.set_password(name, pwtext)
                    player.username = name
                    player.protocol.change_name(player, name)
                    player.message('Account created')

            else:
                player.message('Account name "%s" already in use' % name)
                player.message('Did you mean to /login instead?')

    def login(self, player, r):
        'Try to connect this player with an acromania acount.'

        try:
            name, pwtext = r.split()
        except ValueError:
            player.message('Log in with "/login <name> <password>"')
        else:
            if player.check_password(name, pwtext):
                player.message('Logged in')
                player.username = name
                player.protocol.change_name(player, name)

            else:
                player.message('Incorrect user name or password.')

    def leave(self, player):
        self.players.remove(player)
        if self.game is not None:
            self.game.leave(player)

    def top10(self):
        'Show the top 10 vote getting acros.'

        self.gameserver.top10(self.player)

    def new_game(self, player=None):
        if player is None:
            player = amplayer.DummyPlayer(None, [])

        if self.game is not None and self.game.started:
            game = self.game
            if not player.username and not game.finished:
                player.message('Must log in to restart game.')
                return

            restart = True
            game.finished = True
            game.destroyed = True

        elif self.game is not None and not self.game.started:
            self.game.show_status(player)
            return

        else:
            restart = False

        newgame = amgame.Game(self)
        self.game = newgame

        if restart:
            for p in self.players:
                newgame.join(p)
                p.game = newgame

    def show_players(self, player):
        self.game.show_players(player)

    def add_bot(self, clsname=''):
        if not clsname:
            clsname = 'Bob'

        names = [p.name for p in self.game.players]
        cls = getattr(amplayer, clsname)
        bot = cls(self, names)

        if bot not in self.game.players:
            self.game.join(bot)

    def top10(self, player):
        'send out the top 10 list. If player is None, broadcast to all'

        r = amdb.top10()
        if not r and player is not None:
            player.message('No Top 10 data yet...')
            return

        if player is None:
            send = self.broadcast
        else:
            send = player.message

        send('Top 10 Most Voted for Acros:')
        wvotes = max(len(yellow(row['votes'])) for row in r)
        wplayer = max(len(row['player']) for row in r)
        wletters = max(len(yellow(row['acroletters'])) for row in r)
        for row in r:
            d = dict(votes=yellow(row['votes']),
                        wvotes=wvotes,
                        player=row['player'],
                        wplayer=wplayer,
                        acroletters=yellow(row['acroletters']),
                        wletters=wletters,
                        acrowords=blue(row['acrowords']))
            if hasattr('', 'format'):
                msg = '{votes:>{wvotes}}: {player:{wplayer}}: {acroletters:{wletters}}: {acrowords}'.format(**d)
            else:
                msg = '%*s: %*s: %*s: %s'
                msg = msg % (wvotes, yellow(row['votes']), wplayer, row['player'], wletters, yellow(row['acroletters']), blue(row['acrowords']))
            send(msg)
        send()

    def leaderboard(self, player):
        'send out the leaderboard'

        r = amdb.get_leaders()

        if not r and player is not None:
            player.message('No leaderboard data yet...')
            return

        if player is None:
            send = self.broadcast
        else:
            send = player.message

        send('Leader Board:')
        wname = max(len(row['username']) for row in r)
        maxpoints = max(row['points'] for row in r)
        wpoints = len(yellow(str(maxpoints)))
        for row in r:
            d = dict(name=row['username'],
                        wname=wname,
                        games=row['games'],
                        wins=row['wins'],
                        points=yellow(row['points']),
                        wpoints=wpoints,
                        avg=blue(('%#.4g'%row['avg'])[:5]))
            if hasattr('', 'format'):
                msg = '{name:>{wname}}: ({avg}) {wins} wins in {games} games ({points:>{wpoints}} points)'.format(**d)
            else:
                msg = '%*s: (%s) %i wins in %s games (%*s points)'
                msg = msg % (wname, row['username'], d['avg'], row['wins'], row['games'], wpoints, yellow(row['points']))
            send(msg)
        send()

    def between_games(self):
        'Send some entertaining information out between games'

        if conf.fortune:
            choices = [self.top10, self.leaderboard, self.fortune]
        else:
            choices = [self.top10, self.leaderboard]

        choice = random.choice(choices)
        if hasattr(self, 'between_choice'):
            while choice == self.between_choice:
                choice = random.choice(choices)
        self.between_choice = choice

        game = self.game
        if (game.finished and not game.destroyed) or not game.started:
            choice(None)
            later(conf.between_game_message_freq, self.between_games)

        if game.finished and not game.destroyed:
            self.game_over()
        elif not game.started:
            self.waiting()

    def fortune(self, player):
        import subprocess
        p = subprocess.Popen(conf.fortune, stdout=subprocess.PIPE)
        out, err = p.communicate()
        if not err:
            send = self.broadcast
            send('Fortune:')
            try:
                lines = out.split('\n')
                for line in lines:
                    line = line.rstrip()
                    send(line)
            except:
                send('fortune failed...')

    def game_over(self):
        for player in self.players:
            player.protocol.game_over(player)

    def waiting(self, player=None):
        print 'waiting', player
        if player is not None:
            players = [player]
        else:
            players = self.players

        for player in players:
            player.message('Waiting for more players....')
            player.message()

